﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.MobileServices;
using Newtonsoft.Json;

namespace CeskyRozhlasWP8.Model
{

    //This type is completely public for serialization purposes. 
    public class CachedResponse<T>
    {
        public CachedResponse(IEnumerable<T> iResponse, DateTime iExpirationTime)
        {
            ResponseBody = iResponse;
            ExpirationTime = iExpirationTime;
        }

        public IEnumerable<T> ResponseBody
        {
            get;
            set;
        }

        public DateTime ExpirationTime
        {
            get;
            set;
        }

        public bool IsValid()
        {
            return ExpirationTime > DateTime.Now;
        }
    }
    public abstract class CachedData<T>
    {
        protected readonly IMobileServiceTable<T> MyMobileServiceTable;

        private readonly Dictionary<long, CachedResponse<T>> _cache = new Dictionary<long, CachedResponse<T>>();
        //Cleanup is not implemented.
        //We don't need it, since the instance is recycled oftenly. As soon as it happens to be not the case anymore, 
        //please, add Cleanup() method which removes all expired key-value pairs.

        protected CachedData(MobileServiceClient iMobileServiceClient)
        {
            MyMobileServiceTable = iMobileServiceClient.GetTable<T>();
        }

        public Dictionary<long, CachedResponse<T>> DataSet
        {
            get
            {
                return _cache;
            }
            set
            {
                foreach (var pair in value.Where(pair => pair.Value.IsValid() && !_cache.ContainsKey(pair.Key)))
                {
                    _cache.Add(pair.Key, pair.Value);
                }
            }
        }

        public async Task<CachedResponse<T>> GetCachedResponse(long key)
        {
            lock (this)
            {
                if (_cache.ContainsKey(key))
                {
                    if (_cache[key].IsValid())
                    {
                        return _cache[key];
                    }
                    //else
                    //{
                    //    //We don't want to remove this pair. We just will update the value, since it is significantly cheaper
                    //    //_cache.Remove(key);
                    //}
                }
            }
            return await RefreshCachedResponse(key);
        }

        private async Task<CachedResponse<T>> RefreshCachedResponse(long key)
        {
            var aResponse = await GetResponse(key);
            lock (this)
            {
                _cache[key] = aResponse;
            }
            return _cache[key];
        }

        private async Task<CachedResponse<T>> GetResponse(long key)
        {
            var aResultsSet = await GetRowSet(key);
            return new CachedResponse<T>(aResultsSet, GetExpirationTime(aResultsSet));
        }

        protected virtual DateTime GetExpirationTime(IList<T> aResultsSet)
        {
            //Default expiration time is one day
            return DateTime.Now.AddDays(1);
        }

        protected abstract Task<IList<T>> GetRowSet(long key);
    }

    public class GroupNamesCached : CachedData<GroupName>
    {
        public GroupNamesCached(MobileServiceClient iMobileServiceClient)
            : base(iMobileServiceClient)
        {
        }

        //Groups are valid for the default timespan (1 day), and are not distinguished in cache - only one collection is always returned
        protected override async Task<IList<GroupName>> GetRowSet(long key)
        {
            return await MyMobileServiceTable.OrderBy(item => item.Id).ToCollectionAsync();
        }

        public async Task<IEnumerable<GroupName>> GetAllGroups()
        {
            return (await GetCachedResponse(0)).ResponseBody;
        }

    }

    public class StationPreviewCached : CachedData<PlayingNow>
    {
        public StationPreviewCached(MobileServiceClient iMobileServiceClient)
            : base(iMobileServiceClient)
        {
        }


        protected override async Task<IList<PlayingNow>> GetRowSet(long key)
        {
            return await MyMobileServiceTable.
                Where(station => station.GroupId == key).
                OrderBy(station => station.Id).
                ThenBy(station => station.StopTime).
                ToCollectionAsync();
        }

        protected override DateTime GetExpirationTime(IList<PlayingNow> aSchedule)
        {
            var aExpirationTime = DateTime.Now.AddMinutes(2);
            if (aSchedule.Any())
            {
                aExpirationTime = aSchedule.Where(item => item.StartTime != item.StopTime).Min(item => item.StopTime);
            }
            return aExpirationTime;
        }
    }


    public class StationDescriptionsCached : CachedData<ShortStationDescription>
    {
        public StationDescriptionsCached(MobileServiceClient iMobileServiceClient)
            : base(iMobileServiceClient)
        {
        }

        protected override async Task<IList<ShortStationDescription>> GetRowSet(long key)
        {
            return await MyMobileServiceTable.Where(item => item.Id == key).ToCollectionAsync();
        }
    }

    public class ShowDescriptionsCached : CachedData<DetailedScheduleItem>
    {
        public ShowDescriptionsCached(MobileServiceClient iMobileServiceClient)
            : base(iMobileServiceClient)
        {
        }

        protected override DateTime GetExpirationTime(IList<DetailedScheduleItem> aSchedule)
        {
            var aExpirationTime = DateTime.Now.AddMinutes(2);
            if (aSchedule.Any())
            {
                aExpirationTime = aSchedule.First().StopTime;
            }
            return aExpirationTime;
        }

        protected override async Task<IList<DetailedScheduleItem>> GetRowSet(long key)
        {
            return await MyMobileServiceTable.
                Where(item => item.Id == key).
                ToCollectionAsync();
        }
    }

    public class ShowsScheduleCached : CachedData<DetailedScheduleItem>
    {
        public ShowsScheduleCached(MobileServiceClient iMobileServiceClient)
            : base(iMobileServiceClient)
        {
        }

        protected override DateTime GetExpirationTime(IList<DetailedScheduleItem> aSchedule)
        {
            var aExpirationTime = DateTime.Now.AddMinutes(2);
            if (aSchedule.Any())
            {
                aExpirationTime = aSchedule.First().StopTime;
            }
            return aExpirationTime;
        }

        protected override async Task<IList<DetailedScheduleItem>> GetRowSet(long key)
        {
            return await MyMobileServiceTable
                 .Where(schedule => schedule.StationId == key)
                 .OrderBy(schedule => schedule.StartTime)
                 .ToCollectionAsync();
        }
    }

    public class ShowsData
    {
        public ShowsData()
        {
            var mobileService = new MobileServiceClient(
                "https://ceskyrozhlas.azure-mobile.net/"
                );
            //These tables don't require any caching
            _shows = mobileService.GetTable<DetailedScheduleItem>();
            StreamsTable = mobileService.GetTable<AudioStreamExpanded>();
            BlobsTable = mobileService.GetTable<ScheduleBlob>();

            //These tables are cached
            _groups = new GroupNamesCached(mobileService);
            _stationsPreviewByGroup = new StationPreviewCached(mobileService);
            _stationDescriptions = new StationDescriptionsCached(mobileService);
            _showDescriptions = new ShowDescriptionsCached(mobileService);
            _showsSchedule = new ShowsScheduleCached(mobileService);
        }


        private readonly ShowDescriptionsCached _showDescriptions;
        private readonly GroupNamesCached _groups;
        private readonly StationPreviewCached _stationsPreviewByGroup;
        private readonly StationDescriptionsCached _stationDescriptions;
        //Todo: use information from _showsSchedule for population of _showDescriptions
        private readonly ShowsScheduleCached _showsSchedule;

        #region Public data access for JSON-serialization
        public Dictionary<long, CachedResponse<DetailedScheduleItem>> ShowDescriptions
        {
            get { return _showDescriptions.DataSet; }
            set { _showDescriptions.DataSet = value; }
        }
        public Dictionary<long, CachedResponse<GroupName>> Groups
        {
            get { return _groups.DataSet; }
            set { _groups.DataSet = value; }
        }

        public Dictionary<long, CachedResponse<PlayingNow>> StationsPreviewByGroup
        {
            get { return _stationsPreviewByGroup.DataSet; }
            set { _stationsPreviewByGroup.DataSet = value; }
        }
        public Dictionary<long, CachedResponse<ShortStationDescription>> StationDescriptions
        {
            get { return _stationDescriptions.DataSet; }
            set { _stationDescriptions.DataSet = value; }
        }
        public Dictionary<long, CachedResponse<DetailedScheduleItem>> ShowsSchedule
        {
            get { return _showsSchedule.DataSet; }
            set { _showsSchedule.DataSet = value; }
        }

        #endregion

        #region Debugger-specific hack to troubleshoot download list. This region should not be used when debugger is not attached.
        [JsonIgnore]
        private readonly IMobileServiceTable<DetailedScheduleItem> _shows;

        public async Task<DetailedScheduleItem> GetFirstShow()
        {
            return (await _shows.OrderBy(item => item.StopTime).Take(1).ToEnumerableAsync()).FirstOrDefault();
        }
        #endregion

        //We don't want these instances to be persisted, so mark them as ignored
        [JsonIgnore]
        public readonly IMobileServiceTable<AudioStreamExpanded> StreamsTable;
        [JsonIgnore]
        public readonly IMobileServiceTable<ScheduleBlob> BlobsTable;

        public async Task<DetailedScheduleItem> GetShowById(long iShowId)
        {
            return (await _showDescriptions.GetCachedResponse(iShowId)).ResponseBody.FirstOrDefault();
        }

        public async Task<ShortStationDescription> GetStationById(int stationId)
        {
            return (await _stationDescriptions.GetCachedResponse(stationId)).ResponseBody.FirstOrDefault();
        }

        public async Task<IEnumerable<PlayingNow>> GetStationPreviewByGroupId(int iGroupId)
        {
            return (await _stationsPreviewByGroup.GetCachedResponse(iGroupId)).ResponseBody;
        }


        public async Task<IEnumerable<GroupName>> GetAllGroups()
        {
            return await _groups.GetAllGroups();
        }


        public async Task<IEnumerable<DetailedScheduleItem>> GetScheduleForStation(int iStationId)
        {
            return (await _showsSchedule.GetCachedResponse(iStationId)).ResponseBody;
        }

        private static ShowsData _instance = new ShowsData();

        public static ShowsData Instance {
            get
            {
                if (_instance == null)
                {
                    _instance = new ShowsData();
                }
                return _instance;
            }
        }
    }
}